//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <winhack.h>
#include <elasys_server.h>
#include <elapi.h>
#include <driver.h>
#include <stdlib.h>
#include <string.h>

#include <queue.h>

void EnQueueProc(int eType, int nCode, void *pParam);

class EventQueue : public Queue
{
public:
    int     m_EventType;
    HANDLE  m_hEvent;
    EventQueue(int eType, int Size = 4, int Len = 32) : Queue(Size, Len)
    {
        m_EventType = eType;
        m_hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
        AddNotify(eType, (void*)EnQueueProc);
    }
    ;
};

class EventQueues : public CDeviceDriver
{
public:
    EventQueues(int MaxItem);

    ~EventQueues();

    CARAPI Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf & buffer,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf & inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    // It's Maybe have no use for the keyboard driver.
    virtual void Dispose() {}

public:
    const int MAX_ITEMS;// The actual items
    EventQueue* *Queues; // array for all queues
};

static EventQueues g_EventQueues(EVENT_MAX);

IDeviceDriver *g_pEventQueues = &g_EventQueues;

void EnQueueProc(int eType, int nCode, void *pParam)
{
    //printf("EnQ[%d]: nCode(%d) %x\n", eType, nCode, pParam?*(int *)pParam:0);
    EventQueue *Q = g_EventQueues.Queues[eType];
    if (Q) {
        Q->Enqueue(pParam);
        SetEvent(Q->m_hEvent);
    }
    return;
}

EventQueues::EventQueues(int MaxItem) : MAX_ITEMS( MaxItem )
{
    Queues = new EventQueue* [MAX_ITEMS];
    assert(Queues);
    memset(Queues, 0, sizeof(EventQueue*)*MAX_ITEMS);//Clean all queue
    return ;
}

EventQueues::~EventQueues()
{
    for (int i = 0; i < MAX_ITEMS; i++) {
        if (NULL != g_EventQueues.Queues[i]) {
            CloseHandle(g_EventQueues.Queues[i]->m_hEvent);
            delete g_EventQueues.Queues[i];
        }
    }
    delete[] Queues;
}

ECode EventQueues::Read(
    /* [in] */ Int64 u64Offset, //EventType
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    int EventType = (int)u64Offset;
    bool bASync = (EventType & 0x80000000)?true:false;

    EventType &= 0xffff;
    assert(EventType >= 0 && EventType < EVENT_MAX);

    EventQueue *Q = g_EventQueues.Queues[EventType];
    if (NULL == Q) {
        return E_ACCESS_DENIED;
    }
    if (pBuffer == NULL) {
        return E_INVALID_ARGUMENT;
    }
    assert(pBuffer->GetCapacity() >= Q->ELEM_SIZ);
    int ret = -1;
    while (0 != ret) {
        ret = Q->Dequeue((char*)pBuffer->GetPayload());
        if (0 == ret) {
            pBuffer->SetUsed(Q->ELEM_SIZ);
            break;
        }
        else if (bASync) {//no data
            wprintf(L"EventQueues::Read: At file %d, line %d.\n", __FILE__, __LINE__);
            return E_INVALID_OPERATION;//E_NOT_READY
        }
        WaitForSingleObject(Q->m_hEvent, (DWORD)INFINITE);
    }
    return NOERROR;
}

ECode EventQueues::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode EventQueues::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    ECode ec = E_ACCESS_DENIED;
    int nFuncCode = nControlCode & 0xffff;
    switch (nFuncCode) {
        case 1:{//Create New Queue
            if (inBuffer.GetUsed() >= (int)sizeof(QueueInfo)) {
                QueueInfo *pInfo = (QueueInfo *)inBuffer.GetPayload();
                int e = pInfo->eType;
                if (NULL == g_EventQueues.Queues[e]) {
                    EventQueue *q = new EventQueue(e, pInfo->Size, pInfo->Len);
                    assert(q);
                    g_EventQueues.Queues[pInfo->eType] = q;
                    ec = NOERROR;
                }
            }
            break;
        }
        case 2:{//Reserve: Free queue
            break;
        }
        case 10:{//Drain special Queue data
            int EventType = nControlCode >> 16;
            assert(EventType < 128);
            EventQueue *Q = g_EventQueues.Queues[EventType];

            if (NULL == Q) break;

            if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
            assert(pOutBuffer->GetCapacity() >= Q->ELEM_SIZ);

            //
            int ret, Cnt = 0;
            do {
                ret = Q->Dequeue((char*)pOutBuffer->GetPayload());
                if (0 != ret) break;
                Cnt ++;
            } while (1);

            if (Cnt) {
                pOutBuffer->SetUsed(Q->ELEM_SIZ);
            }
            ec = NOERROR;
            break;
        }
        default:
            break;
    }
    return ec;
}

EXTERN IDeviceDriver * CDECL CreateEventQueues(uint_t uDeviceNo, void *pvParameter)
{
    EventQueue *kq = new EventQueue(EVENT_KEYPAD, 8/*sizeof(KPD_DATA)*/, 64);
    g_EventQueues.Queues[EVENT_KEYPAD] = kq;

    EventQueue *tq = new EventQueue(EVENT_TOUCH, 6/*sizeof(TOUCH_DATA)*/, 32);
    g_EventQueues.Queues[EVENT_TOUCH] = tq;

    EventQueue *sdq = new EventQueue(EVENT_MiniSD, 4/*sizeof(MINISD_DATA)*/, 8);
    g_EventQueues.Queues[EVENT_MiniSD] = sdq;

    //printf("Create EventQueues %x\n", &g_EventQueues);
    return (IDeviceDriver *)&g_EventQueues;
}
